/*
 *  PassBag is multi-platform software that securely manage/store passwords
 *  Copyright (C) <2009>  <Hugo Rodrigues PEREIRA>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *	
 *  You can find a copy of the GPLv3 at project <root directory>/gpl.txt
 *
 */ 
 
using System;
using System.Collections.Generic;
using System.IO;
using ICSharpCode.SharpZipLib.Zip;
using ICSharpCode.SharpZipLib.Zip.Compression;


namespace PassBag.Logic.Backup 
{
    /// <summary>
	/// Summary description for BackupSet.
	/// </summary>
	public class BackupSet
	{
        /// <summary>
        /// version off the Backup Set, not of passbag. A newer version of passbag can contain
        /// the same Backup Set version
        /// </summary>
        private const string PASSBAG_FILE_COMMENT = "PassBag-BackupSet-v1.0";
        private const string INVALIDE_PASSBAG_BACKUP = "Not a passbag backup file";
        private const string BACKUP_SET_ZIP_PASSWORD = "js=?wj\"*(&)4%+-_0$!";

        /// <summary>
        /// If the zip file not contain the PASSBAG_FILE_COMMENT has comment it's an
        /// invalid backup file
        /// </summary>
        /// <remarks>
        /// When read the zip file, catch Exception, if the exception message is InvalidBackupFileException
        /// then the zip file is not a backup set archive
        /// </remarks>
        public static string InvalidBackupFileException {
            get {
                return INVALIDE_PASSBAG_BACKUP;
            }
        } 

        #region public method

        /// <summary>
        /// Extracts a zip entry from the backup set.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <param name="entryName">Name of the entry.</param>
        /// <param name="newEntryName">New name of the entry.</param>
        /// <returns>Return true when success, else false</returns>
        public static bool ExtractZipEntry (string fileName, string entryName, string newEntryName) {
            using (FileStream diskFile = File.OpenRead (fileName)) {
                using (ZipFile inStream = new ZipFile (diskFile)) {
                    inStream.Password = BACKUP_SET_ZIP_PASSWORD;

                    if (inStream.ZipFileComment.Equals (PASSBAG_FILE_COMMENT)) {

                        try {
                            ZipEntry entry = inStream.GetEntry (entryName);

                            using (Stream stream = inStream.GetInputStream (entry)) {
                                int streamLen = Convert.ToInt32(entry.Size);
                                byte[] byteStream = new byte[streamLen];

                                stream.Read (byteStream, 0, streamLen);

                                using (FileStream fs = File.Open (newEntryName, FileMode.Create, FileAccess.ReadWrite, FileShare.Read)) {
                                    fs.Write (byteStream, 0, streamLen);
                                }
                            }
                            return true;

                        }
                        catch {
                            return false;
                        }
                    }
                    else {
                        throw new ZipException (INVALIDE_PASSBAG_BACKUP);
                    }
                }
            }
        }

        /// <summary>
        /// Gets the list of entry on the backup set
        /// </summary>
        /// <param name="fileName">Backup set file name</param>
        /// <returns>The list of BackupZipEntry</returns>
        public static List<BackupZipEntry> GetBackupFleList (string fileName) {
            List<BackupZipEntry> zipEntryList = new List<BackupZipEntry> ();

            using (FileStream diskFile = File.OpenRead (fileName)) {
                using (ZipFile inStream = new ZipFile (diskFile)) {
                    inStream.Password = BACKUP_SET_ZIP_PASSWORD;
                    
                    if (inStream.ZipFileComment.Equals(PASSBAG_FILE_COMMENT)) {
                        for (int i = 0; i < inStream.Count; i++) {
                            ZipEntry entry = inStream[i];
                            zipEntryList.Add (new BackupZipEntry (entry.Name, entry.Comment));
                        }
                	}
                    else {
                        throw new ZipException (INVALIDE_PASSBAG_BACKUP);
                    }
                }    
            }

            return zipEntryList;
   
        }

        /// <summary>
        /// Creates the zip files "Backup set".
        /// </summary>
        /// <param name="zipFileName">Name of the zip file.</param>
        /// <param name="fileList">The file list.</param>
        /// <returns>Return true when success, else false</returns>
        public static bool CreateZipFiles (string zipFileName, List<BackupZipEntry> fileList) {
            return (CreateZipFiles (zipFileName, fileList, false, "", BACKUP_SET_ZIP_PASSWORD));
        }


        /// <summary>
        /// Creates the zip files "Backup set".
        /// </summary>
        /// <param name="zipFileName">Name of the zip file.</param>
        /// <param name="fileList">The file list.</param>
        /// <param name="path">The path.</param>
        /// <returns>Return true when success, else false</returns>
        public static bool CreateZipFiles (string zipFileName, List<BackupZipEntry> fileList, string path) {
            return (CreateZipFiles (zipFileName, fileList, false, path, BACKUP_SET_ZIP_PASSWORD));
        }

        /// <summary>
        /// Gets the name from the full file name.
        /// </summary>
        /// <param name="fullName">The full name.</param>
        /// <returns>The file name without path</returns>
        public static string GetFileName (string fullName) {
            FileInfo fi = new FileInfo (fullName);
            return fi.Name;
        }


        /// <summary>
        /// Gets the file path.
        /// </summary>
        /// <param name="fullName">The full name.</param>
        /// <returns>The path name without the file name.</returns>
        public static string GetFilePath (string fullName) {
            FileInfo fi = new FileInfo (fullName);
            return fi.DirectoryName;
        }

        /// <summary>
        /// Adds the file to archive.
        /// </summary>
        /// <param name="zipFileName">Name of the zip file.</param>
        /// <param name="newFile">The new BackupZipEntry.</param>
        public static void AddFileToArchive (string zipFileName, BackupZipEntry newFile) {
            using (ZipFile zipFile = new ZipFile (zipFileName)) {
                zipFile.BeginUpdate ();

                zipFile.Password = BACKUP_SET_ZIP_PASSWORD;
                zipFile.SetComment (PASSBAG_FILE_COMMENT);
                zipFile.UseZip64 = UseZip64.Off;

                zipFile.Add (newFile.FileName, BackupZipEntry.GetUniqueZipFileEntryName(GetFileName (newFile.FileName)), newFile.Comment);

                zipFile.CommitUpdate ();
            }
        }


        #endregion
        #region private methods

        /// <summary>
        /// Internal zip file function
        /// </summary>
        /// <param name="zipFileName">Zip file name</param>
        /// <param name="newFile">List<BackupZipEntry> that contain the files to zip</param>
        /// <param name="addFullPath">If true, the file contain the fullpath on the zip file</param>
        /// <param name="path">if addFullPath and path is empty, the files are added to zip file root. If not empty the file are added on the file specified on path arg</param>
        /// <param name="password">if not empty add password to zip file</param>
        /// <returns></returns>
        private static bool CreateZipFiles (string zipFileName, List<BackupZipEntry> fileList, bool addFullPath, string path, string password) {
            FileStream zfs = null;
            ZipOutputStream zs = null;


            try {
                if (System.IO.File.Exists (zipFileName))
                    zfs = System.IO.File.OpenWrite (zipFileName);
                else
                    zfs = System.IO.File.Create (zipFileName);

                zs = new ZipOutputStream (zfs);

                if (!string.IsNullOrEmpty (password)) {
                    zs.Password = password;
                }

                // compression settings
                zs.UseZip64 = UseZip64.Off;
                zs.SetLevel (Deflater.DEFAULT_COMPRESSION);
                zs.SetComment(PASSBAG_FILE_COMMENT);
                
                foreach (BackupZipEntry zipFile in fileList) {
                    ZipEntry ze;

                    if (addFullPath) {
                        // the ZipEntry requires a preceding slash if the file is a file
                        if (zipFile.FileName.IndexOf (System.IO.Path.DirectorySeparatorChar) > -1 && !zipFile.FileName.StartsWith (System.IO.Path.DirectorySeparatorChar.ToString ()))
                            ze = new ZipEntry (System.IO.Path.DirectorySeparatorChar + zipFile.FileName);
                        else
                            ze = new ZipEntry (zipFile.FileName);

                    }
                    else {
                        if (path.Length != 0) {
                            ze = new ZipEntry ((path.EndsWith (System.IO.Path.DirectorySeparatorChar.ToString ()) ? path : path + System.IO.Path.DirectorySeparatorChar) + GetFileName (zipFile.FileName));
                        }
                        else {
                            ze = new ZipEntry (BackupZipEntry.GetUniqueZipFileEntryName(GetFileName (zipFile.FileName)));
                        }

                    }

                    ze.Comment = zipFile.Comment;
                    
                    ze.DateTime = DateTime.Now;
                   
                    zs.PutNextEntry (ze);

                    FileStream fs = null;

                    try {
                        fs = System.IO.File.OpenRead (zipFile.FileName);

                        byte[] buffer = new byte[2048];
                        int len = fs.Read (buffer, 0, buffer.Length);

                        while (len > 0) {
                            zs.Write (buffer, 0, len);
                            len = fs.Read (buffer, 0, buffer.Length);
                        }

                    }
                    catch (System.Exception ex) {
                        throw (new System.Exception (ex.Message, ex.InnerException));
                    }
                    finally {
                        if (fs != null)
                            fs.Close ();

                        zs.CloseEntry ();
                    }
                }

            }
            finally {
                if (zs != null)
                    zs.Close ();
                if (zfs != null)
                    zfs.Close ();
            }

            return (true);
        }
        
        #endregion
    }
}
